package cn.geminis.crypto.csp.soft.rsa;

import cn.geminis.core.exception.CommonException;
import cn.geminis.core.util.FileUtils;
import cn.geminis.crypto.core.key.PrivateKey;
import cn.geminis.crypto.core.key.PublicKey;
import cn.geminis.crypto.csp.*;
import cn.geminis.crypto.csp.soft.SoftRandomGenerator;
import cn.geminis.crypto.csp.soft.ec.Sha256Digest;
import cn.geminis.crypto.csp.soft.ec.Sha256HMac;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECNamedDomainParameters;

import java.security.SecureRandom;
import java.util.Arrays;

/**
 * @author Allen
 */
public class RsaCspFactory extends AbstractCspFactory {

    private static final String PRIVATE_KEY_FILE_NAME = "rsaPrivateKey.dat";
    private static final String PUBLIC_KEY_FILE_NAME = "rsaPublicKey.dat";
    private static final String PRIVATE_KEY_FILE_NAME_EC = "ecPrivateKey.dat";
    private static final String PUBLIC_KEY_FILE_NAME_EC = "ecPublicKey.dat";

    protected final PrivateKey privateKey;
    protected final PublicKey publicKey;
    protected final PrivateKey ecPrivateKey;
    protected final PublicKey ecPublicKey;
    private final int moduleLength;
    private final byte[] mainKey;

    public RsaCspFactory(String keyPath, String pin, String moduleLength) {
        // 私钥加密密钥为口令的摘要值
        var digest = this.createDigest();
        var pinDigest = digest.digest(pin.getBytes());
        this.mainKey = Arrays.copyOf(pinDigest, 32);

        var cipher = this.createBlockCipher();

        var pkFileName = keyPath + PUBLIC_KEY_FILE_NAME;
        var skFileName = keyPath + PRIVATE_KEY_FILE_NAME;
        var ecPkFileName = keyPath + PUBLIC_KEY_FILE_NAME_EC;
        var ecSkFileName = keyPath + PRIVATE_KEY_FILE_NAME_EC;
        this.moduleLength = Integer.parseInt(moduleLength);

        var pkData = FileUtils.readFile(pkFileName);
        var skData = FileUtils.readFile(skFileName);
        var ecPkData = FileUtils.readFile(ecPkFileName);
        var ecSkData = FileUtils.readFile(ecSkFileName);

        if (pkData == null || skData == null) {
            // 没有密钥，创建
            try (var generator = new RsaKeyGenerator(this.moduleLength)) {
                var keypair = generator.generateKeyPair();
                pkData = keypair.getPublicKey().getEncoded();
                skData = keypair.getPrivateKey().getEncoded();

                skData = cipher.encrypt(skData);

                FileUtils.writeFile(pkFileName, pkData);
                FileUtils.writeFile(skFileName, skData);
            }
        }

        if (ecPkData == null || ecSkData == null) {
            // 没有密钥，创建
            var name = "P-256";
            var oid = ECNamedCurveTable.getOID(name);
            var ecSpec = ECNamedCurveTable.getByName(name);
            var domainParameters = new ECNamedDomainParameters(oid, ecSpec.getCurve(), ecSpec.getG(), ecSpec.getN());
            var generationParameters = new ECKeyGenerationParameters(domainParameters, new SecureRandom());
            var keyPairGenerator = new ECKeyPairGenerator();
            keyPairGenerator.init(generationParameters);

            var keyPair = keyPairGenerator.generateKeyPair();
            ecPkData = new PublicKey(keyPair.getPublic()).getEncoded();
            ecSkData = new PrivateKey(keyPair.getPrivate()).getEncoded();
            ecSkData = cipher.encrypt(ecSkData);

            FileUtils.writeFile(ecPkFileName, ecPkData);
            FileUtils.writeFile(ecSkFileName, ecSkData);
        }

        try {
            skData = cipher.decrypt(skData);
            ecSkData = cipher.decrypt(ecSkData);
        } catch (Exception e) {
            throw new CommonException("解密PIN码保护的私钥错误", e);
        }

        this.privateKey = new PrivateKey(skData);
        this.publicKey = new PublicKey(pkData);
        this.ecPrivateKey = new PrivateKey(ecSkData);
        this.ecPublicKey = new PublicKey(ecPkData);

        register();
    }

    @Override
    public RandomGenerator createRandomGenerator() {
        return new SoftRandomGenerator();
    }

    @Override
    public String getDigestAlgOid() {
        return NISTObjectIdentifiers.id_sha256.toString();
    }

    @Override
    public String getDigestAlgName() {
        return "SHA256";
    }

    @Override
    public AbstractDigest createDigest() {
        return new Sha256Digest();
    }

    @Override
    public String getSignerAlgOid() {
        return PKCSObjectIdentifiers.sha256WithRSAEncryption.toString();
    }

    @Override
    public String getAsyncEncryptionAlgOid() {
        return PKCSObjectIdentifiers.rsaEncryption.toString();
    }

    @Override
    public String getSignerAlgName() {
        return "Sha256WithRSA";
    }

    @Override
    public AbstractSigner createSigner() {
        return new Sha256WithRsaSigner(publicKey, privateKey);
    }

    @Override
    public AbstractBlockCipher createBlockCipher() {
        return new AesBlockCipher(this.mainKey);
    }

    @Override
    public AbstractAsymmetricBlockCipher createAsymmetricBlockCipher() {
        return new RsaBlockCipher(this.publicKey, this.privateKey);
    }

    @Override
    public KeyPairGenerator createKeyPairGenerator() {
        return new RsaKeyGenerator(this.moduleLength);
    }

    @Override
    public AbstractAgreement createAgreement() {
        return new ECDHAgreement(this.ecPublicKey, this.ecPrivateKey);
    }

    @Override
    public AbstractMac createMac() {
        return new Sha256HMac();
    }

    @Override
    public String getBlockCipherAlgOid() {
        return NISTObjectIdentifiers.aes.toString();
    }

    @Override
    public String getKeyPairOid() {
        return "1.2.840.113549.1.1.1";
    }

    @Override
    public void close() {
        //nothing to close
    }
}
